<?php

/*
 * Copyright (c) 2012-2013 Aalto University and University of Helsinki
 * MIT License
 * see LICENSE.txt for more information
 */

require_once('controller/Controller.php');

class RestController extends Controller{
  
  private function return_error($code, $status, $message) {
    header("Status: $code");
    header("Content-type: text/plain");
    echo "$code $status : $message";
    exit();
  }
  
  private function return_json($data) {
    if (isset($_GET['callback'])) {
      header("Content-type: application/javascript");
      // wrap with JSONP callback
      echo $_GET['callback'] . "(" . json_encode($data) . ");";
    } else {
      header("Content-type: application/json");
      echo json_encode($data);
    }
  }

  private function parseURI() {
    if (isset($_GET['uri'])) return $_GET['uri'];
    return $this->return_error(400, "Bad Request", "uri parameter missing");
  }

  private function parseLimit() {
    $limit = isset($_GET['limit']) ?
             intval($_GET['limit']) : DEFAULT_TRANSITIVE_LIMIT;
    if ($limit <= 0)
      $this->return_error(400, "Bad Request", "Invalid limit parameter");
    return $limit;
  }

  /**
   * Determine the language to use, from the lang URL parameter (if set),
   * otherwise from the default language of the current vocabulary.
   * As a side effect, set this language as current language.
   * @return string current language
   */
  private function getAndSetLanguage($vocabId) {
    $lang = isset($_GET['lang']) ?
            $_GET['lang'] : 
            $this->model->getVocabulary($vocabId)->getDefaultLanguage();
    $this->setLanguageProperties($lang);
    return $lang;
  }

/** Global REST methods **/

  public function vocabularies() {
    if (!isset($_GET['lang']))
      return $this->return_error(400, "Bad Request", "lang parameter missing");
    $lang = $_GET['lang'];
    $this->setLanguageProperties($lang);

    $vocabs = array();
    foreach ($this->model->getVocabularies() as $voc) {
      $vocabs[$voc->getId()] = $voc->getTitle();
    }
    ksort($vocabs);
    $results = array();
    foreach ($vocabs as $id => $title) {
      $results[] = array(
        'uri' => $id,
        'id' => $id,
        'title' => $title);
    }

    /* encode the results in a JSON-LD compatible array */
    $ret = array(
        '@context' => array(
            'onki' => 'http://schema.onki.fi/onki#',
            'title' => array('@id'=>'rdfs:label', '@language'=>$lang),
            'vocabularies' => 'onki:hasVocabulary',
            'id' => 'onki:vocabularyIdentifier',
            'uri' => '@id',
        ),
        'uri' => '',
        'vocabularies' => $results,
    );
    
    return $this->return_json($ret);
  }

  public function search($vocab=null) {
    if(isset($_GET['query']))
      $term = $_GET['query'];
    else
      return $this->return_error(400, "Bad Request", "query parameter missing");
    if(isset($_GET['maxhits']) && (!is_numeric($_GET['maxhits']) || $_GET['maxhits'] <= 0)) {
      return $this->return_error(400, "Bad Request", "maxhits parameter is invalid");
    }
    if(isset($_GET['offset']) && (!is_numeric($_GET['offset']) || $_GET['offset'] < 0)) {
      return $this->return_error(400, "Bad Request", "offset parameter is invalid");
    }
    
    $vocid = isset($_GET['vocab']) ? $_GET['vocab'] : $vocab; # optional
    $lang = isset($_GET['lang']) ? $_GET['lang'] : null; # optional
    $type = isset($_GET['type']) ? $_GET['type'] : 'skos:Concept';
    $parent = isset($_GET['parent']) ? $_GET['parent'] : null;
    $group = isset($_GET['group']) ? $_GET['group'] : null;

    $maxhits = isset($_GET['maxhits']) ? ($_GET['maxhits']) : null; # optional
    $offset = isset($_GET['offset']) ? ($_GET['offset']) : 0; # optional

    $results = $this->model->searchConcepts($term, $vocid, $lang, $type, $parent, $group, $offset, $maxhits);
    // before serializing to JSON, get rid of the Vocabulary object that came with each resource
    foreach($results as &$res)
      unset($res['voc']);

    $ret = array(
        '@context' => array(
            'skos' => 'http://www.w3.org/2004/02/skos/core#',
            'onki' => 'http://schema.onki.fi/onki#',
            'uri' => '@id',
            'results' => array(
                '@id' => 'onki:results',
                '@container' => '@list',
            ),
            'prefLabel' => 'skos:prefLabel',
            'altLabel' => 'skos:altLabel',
        ),
        'uri' => '',
        'results' => $results,
    );
    
    if ($lang)
      $ret['@context']['@language'] = $lang;
    
    return $this->return_json($ret);
  }

/** Vocabulary-specific methods **/

  public function vocabularyInformation($vocabId) {
    $lang = $this->getAndSetLanguage($vocabId);
    $vocab = $this->model->getVocabulary($vocabId);

    /* encode the results in a JSON-LD compatible array */
    $conceptschemes = array();
    foreach($vocab->getConceptSchemes() as $uri => $csdata) {
      $csdata['uri'] = $uri;
      $csdata['type'] = 'skos:ConceptScheme';
      $conceptschemes[] = $csdata;
    }
    
    $ret = array(
        '@context' => array(
            'skos' => 'http://www.w3.org/2004/02/skos/core#',
            'onki' => 'http://schema.onki.fi/onki#',
            'dct' => 'http://purl.org/dc/terms/',
            'uri' => '@id',
            'type' => '@type',
            'title' => 'rdfs:label',
            'conceptschemes' => 'onki:hasConceptScheme',
            'id' => 'onki:vocabularyIdentifier',
            'defaultLanguage' => 'onki:defaultLanguage',
            'languages' => 'onki:language',
            'label' => 'rdfs:label',
            'prefLabel' => 'skos:prefLabel',
            'title' => 'dct:title',
            '@language' => $lang,
        ),
        'uri' => '',
        'id' => $vocabId,
        'title' => $vocab->getTitle(),
        'defaultLanguage' => $vocab->getDefaultLanguage(),
        'languages' => $vocab->getLanguages(),
        'conceptschemes' => $conceptschemes,
    );

    return $this->return_json($ret);
  }

  public function types($vocabId) {
    $lang = $this->getAndSetLanguage($vocabId);
    $vocab = $this->model->getVocabulary($vocabId);

    /* encode the results in a JSON-LD compatible array */
    $types = array();
    foreach($vocab->getTypes() as $uri => $typedata) {
      $type = array_merge(array('uri' => $uri), $typedata);
      $types[] = $type;
    }
    
    $ret = array(
        '@context' => array(
            'skos' => 'http://www.w3.org/2004/02/skos/core#',
            'onki' => 'http://schema.onki.fi/onki#',
            'uri' => '@id',
            'type' => '@type',
            'label' => 'rdfs:label',
            'superclass' => array('@id' => 'rdfs:subClassOf', '@type' => '@id'),
            'types' => 'onki:hasType',
            '@language' => $lang,
        ),
        'uri' => '',
        'types' => $types,
    );

    return $this->return_json($ret);
  }
  
  
  public function lookup($vocid) {
    if(isset($_GET['label']))
      $label = $_GET['label'];
    else
      return $this->return_error(400, "Bad Request", "label parameter missing");
    $lang = isset($_GET['lang']) ? $_GET['lang'] : null; # optional
    $vocab = $this->model->getVocabulary($vocid);
    if ($label == '') $this->return_error(400, 'Bad Request', 'empty label');

    $results = $this->model->searchConcepts($label, $vocid, $lang);

    $hits = array();    
    // case 1: exact match on preferred label
    foreach ($results as $res)
      if ($res['prefLabel'] == $label)
        $hits[] = $res;

    // case 2: case-insensitive match on preferred label
    if (sizeof($hits) == 0) { // not yet found
      foreach ($results as $res)
        if (strtolower($res['prefLabel']) == strtolower($label))
          $hits[] = $res;
    }
    
    // case 3: exact match on alternate label
    if (sizeof($hits) == 0) { // not yet found
      foreach ($results as $res)
        if (isset($res['altLabel']) && $res['altLabel'] == $label)
          $hits[] = $res;
    }

    // case 4: case-insensitive match on alternate label
    if (sizeof($hits) == 0) { // not yet found
      foreach ($results as $res)
        if (isset($res['altLabel']) && strtolower($res['altLabel']) == strtolower($label))
          $hits[] = $res;
    }
    
    if (sizeof($hits) == 0) // no matches found
      return $this->return_error('404', 'Not Found', "Could not find label '$label'");

    // did find some matches!
    // get rid of Vocabulary objects
    foreach($hits as &$res)
      unset($res['voc']);

    $ret = array(
        '@context' => array(
            'skos' => 'http://www.w3.org/2004/02/skos/core#',
            'onki' => 'http://schema.onki.fi/onki#',
            'results' => array(
                '@id' => 'onki:results',
            ),
            'uri' => '@id',
            'prefLabel' => 'skos:prefLabel',
            'altLabel' => 'skos:altLabel',
        ),
        'uri' => '',
        'results' => $hits,
    );
    if ($lang)
      $ret['@context']['@language'] = $lang;
    
    return $this->return_json($ret);
  }

  public function topConcepts($vocabId){
    $lang = $this->getAndSetLanguage($vocabId);
    $vocab = $this->model->getVocabulary($vocabId);
    $scheme = isset($_GET['scheme']) ? $_GET['scheme'] : $vocab->getDefaultConceptScheme();

    /* encode the results in a JSON-LD compatible array */
    $topconcepts = $vocab->getTopConcepts($scheme);
    $results = array();
    foreach($topconcepts as $uri => $label) {
      $results[] = array('uri'=>$uri, 'label'=>$label);
    }
    
    $ret = array(
        '@context' => array(
            'skos' => 'http://www.w3.org/2004/02/skos/core#',
            'onki' => 'http://schema.onki.fi/onki#',
            'uri' => '@id',
            'topconcepts' => 'skos:hasTopConcept',
            'label' => 'skos:prefLabel',
            '@language' => $lang,
        ),
        'uri' => $scheme,
        'topconcepts' => $results,
    );
    
    return $this->return_json($ret);
  }

  public function data($vocab) {
    if (isset($_GET['uri'])) {
      $uri = $_GET['uri'];
    } else { // whole vocabulary - redirect to SPARQL HTTP Graph Store
      $url = $this->model->getVocabulary($vocab)->getDataURL();
      header("Location: " . $url);
      return;
    }

    if (isset($_GET['format'])) {
      $format = $_GET['format'];
    } else {
       $accept_header = $_SERVER['HTTP_ACCEPT'];
       $accept_header = explode(',', $accept_header);
      
      $format = 'application/rdf+xml';
      if (in_array('text/turtle', $accept_header)) {
        $format = 'text/turtle';
      } elseif (in_array('application/json', $accept_header)) {
        $format = 'application/json';
      }
    }
    $results = $this->model->getVocabulary($vocab)->getRDF($uri, $format);

    header("Content-type: " . $format);
    echo $results;
  }

  public function broader($vocabId){
    $lang = $this->getAndSetLanguage($vocabId);
    $vocab = $this->model->getVocabulary($vocabId);
    $uri = $this->parseURI();

    $results = array();
    foreach ($vocab->getConceptBroaders($uri) as $object => $vals) {
      $results[] = array('uri'=>$object, 'prefLabel'=>$vals['label']);
    }
    
    $ret = array(
        '@context' => array(
            'skos' => 'http://www.w3.org/2004/02/skos/core#',
            'uri' => '@id',
            'prefLabel' => 'skos:prefLabel',
            'broader' => 'skos:broader',
            '@language' => $lang,
        ),
        'uri' => $uri,
        'broader' => $results,
    );

    return $this->return_json($ret);
  }
  
  public function broaderTransitive($vocabId){
    $lang = $this->getAndSetLanguage($vocabId);
    $vocab = $this->model->getVocabulary($vocabId);
    $uri = $this->parseURI();
    $limit = $this->parseLimit();

    $results = array();
    foreach ($vocab->getConceptTransitiveBroaders($uri, $limit) as $buri => $vals) {
      $result = array('uri'=>$buri, 'prefLabel'=>$vals['label']);
      if (isset($vals['direct'])) {
        $result['broader'] = array();
        foreach ($vals['direct'] as $duri => $dval) {
          $result['broader'][] = $duri;
        }
      }
      $results[$buri] = $result;
    }
    
    $ret = array(
        '@context' => array(
            'skos' => 'http://www.w3.org/2004/02/skos/core#',
            'uri' => '@id',
            'type' => '@type',
            'prefLabel' => 'skos:prefLabel',
            'broader' => array('@id'=>'skos:broader','@type'=>'@id'),
            'broaderTransitive' => array('@id'=>'skos:broaderTransitive','@container'=>'@index'),
            '@language' => $lang,
        ),
        'uri' => $uri,
        'broaderTransitive' => $results,
    );

    return $this->return_json($ret);
  }

  public function narrower($vocabId){
    $lang = $this->getAndSetLanguage($vocabId);
    $vocab = $this->model->getVocabulary($vocabId);
    $uri = $this->parseURI();

    $results = array();
    foreach ($vocab->getConceptNarrowers($uri) as $object => $vals) {
      $results[] = array('uri'=>$object, 'prefLabel'=>$vals['label']);
    }
    
    $ret = array(
        '@context' => array(
            'skos' => 'http://www.w3.org/2004/02/skos/core#',
            'uri' => '@id',
            'prefLabel' => 'skos:prefLabel',
            'narrower' => 'skos:narrower',
            '@language' => $lang,
        ),
        'uri' => $uri,
        'narrower' => $results,
    );

    return $this->return_json($ret);
  }
  
  public function narrowerTransitive($vocabId){
    $lang = $this->getAndSetLanguage($vocabId);
    $vocab = $this->model->getVocabulary($vocabId);
    $uri = $this->parseURI();
    $limit = $this->parseLimit();
    isset($_GET['limit']) ?
             intval($_GET['limit']) : DEFAULT_TRANSITIVE_LIMIT;
    if ($limit <= 0) $this->return_error(400, "Bad Request", "Invalid limit parameter");

    $results = array();
    foreach ($vocab->getConceptTransitiveNarrowers($uri, $limit) as $nuri => $vals) {
      $result = array('uri'=>$nuri, 'prefLabel'=>$vals['label']);
      if (isset($vals['direct'])) {
        $result['narrower'] = array();
        foreach ($vals['direct'] as $duri => $dval) {
          $result['narrower'][] = $duri;
        }
      }
      $results[$nuri] = $result;
    }
    
    $ret = array(
        '@context' => array(
            'skos' => 'http://www.w3.org/2004/02/skos/core#',
            'uri' => '@id',
            'type' => '@type',
            'prefLabel' => 'skos:prefLabel',
            'narrower' => array('@id'=>'skos:narrower','@type'=>'@id'),
            'narrowerTransitive' => array('@id'=>'skos:narrowerTransitive','@container'=>'@index'),
            '@language' => $lang,
        ),
        'uri' => $uri,
        'narrowerTransitive' => $results,
    );

    return $this->return_json($ret);
  }

  public function hierarchy($vocabId){
    $lang = $this->getAndSetLanguage($vocabId);
    $vocab = $this->model->getVocabulary($vocabId);
    $uri = $this->parseURI();

    $results = $this->model->getVocabulary($vocabId)->getConceptHierarchy($uri);
    
    $ret = array(
        '@context' => array(
            'skos' => 'http://www.w3.org/2004/02/skos/core#',
            'onki' => 'http://schema.onki.fi/onki#',
            'uri' => '@id',
            'type' => '@type',
            'prefLabel' => 'skos:prefLabel',
            'narrower' => array('@id'=>'skos:narrower','@type'=>'@id'),
            'broader' => array('@id'=>'skos:broader','@type'=>'@id'),
            'broaderTransitive' => array('@id'=>'skos:broaderTransitive','@container'=>'@index'),
            'top' => array('@id'=>'skos:topConceptOf','@type'=>'@id'),
            'hasChildren' => 'onki:hasChildren',
            '@language' => $lang,
        ),
        'uri' => $uri,
        'broaderTransitive' => $results,
    );
    
    return $this->return_json($ret);
  }

  public function children($vocabId){
    $lang = $this->getAndSetLanguage($vocabId);
    $vocab = $this->model->getVocabulary($vocabId);
    $uri = $this->parseURI();

    $children = $vocab->getConceptChildren($uri);
    
    $ret = array(
        '@context' => array(
            'skos' => 'http://www.w3.org/2004/02/skos/core#',
            'uri' => '@id',
            'type' => '@type',
            'prefLabel' => 'skos:prefLabel',
            'narrower' => 'skos:narrower',
            'hasChildren' => 'onki:hasChildren',
            '@language' => $lang,
        ),
        'uri' => $uri,
        'narrower' => $children,
    );

    return $this->return_json($ret);
  }
  
  public function related($vocabId){
    $lang = $this->getAndSetLanguage($vocabId);
    $vocab = $this->model->getVocabulary($vocabId);
    $uri = $this->parseURI();

    $results = array();
    foreach ($vocab->getConceptRelateds($uri) as $uri => $vals) {
      $results[] = array('uri'=>$uri, 'prefLabel'=>$vals['label']);
    }
    
    $ret = array(
        '@context' => array(
            'skos' => 'http://www.w3.org/2004/02/skos/core#',
            'uri' => '@id',
            'type' => '@type',
            'prefLabel' => 'skos:prefLabel',
            'related' => 'skos:related',
            '@language' => $lang,
        ),
        'uri' => $uri,
        'related' => $results,
    );

    return $this->return_json($ret);
  }
}
